maxvogt-analysis: Uetikon

The code in this notebook is based on Martin Fleischmann’s 2021 workshop Capturing the Structure of Cities with Data Science (SDSC), licensed under the CC BY-SA 4.0 license. © 2021 Martin Fleischmann.

The geodata in this notebook is retrieved from OpenStreetMap. © OpenStreetMap Contributors.


This notebook contains geodata and analyses for the following items of the Max Vogt collection by Moritz Twente and Luisa Omonsky: - UET030 - UET130


Open in an interactive in-browser environment:

Binder

Binder
import warnings

import geopandas
import libpysal
import momepy
import osmnx
import pandas

from clustergram import Clustergram

import matplotlib.pyplot as plt
from bokeh.io import output_notebook
from bokeh.plotting import show

Pick a place, ideally a town with a good coverage in OpenStreetMap and its local CRS.

place = 'Uetikon'
point = 47.25902, 8.67916
address = 'Alte Landstrasse 144, 8707 Uetikon am See'
dist = 2000
local_crs = 'EPSG:2056'
geopandas.tools.geocode(address).explore()
Make this Notebook Trusted to load map: File -> Trust Notebook

Input data

Download data from OpenStreetMap.

Buildings

#buildings = osmnx.features.features_from_place(place, tags={'building':True})
#buildings = osmnx.features.features_from_address(address, tags={'building':True}, dist=dist)
buildings = osmnx.features.features_from_point(point, tags={'building':True}, dist=dist)
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/shapely/set_operations.py:340: RuntimeWarning: invalid value encountered in union
  return lib.union(a, b, **kwargs)
buildings
geometry access addr:city addr:housenumber addr:postcode addr:street shop building name opening_hours ... payment:lightning_contactless payment:onchain weather_protection female male architect:wikidata ways type park_ride supervised
element_type osmid
node 444628323 POINT (8.69036 47.25319) NaN NaN NaN NaN NaN NaN public Betreibungsamt NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
5630876041 POINT (8.69762 47.25597) NaN NaN NaN NaN NaN NaN yes NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
way 21642426 POLYGON ((8.67801 47.26433, 8.67796 47.26394, ... NaN Uetikon am See 122 8707 Bergstrasse NaN school Sporthalle Riedwies NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
23793021 POLYGON ((8.70555 47.25559, 8.70582 47.25548, ... NaN Männedorf 17 8708 Birkenstrasse NaN apartments NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
23793023 POLYGON ((8.70581 47.25580, 8.70554 47.25591, ... NaN Männedorf 5 8708 Birkenstrasse NaN apartments NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
relation 1122229 POLYGON ((8.67891 47.26474, 8.67883 47.26456, ... NaN NaN NaN NaN NaN NaN yes St. Franziskus NaN ... NaN NaN NaN NaN NaN NaN [40864321, 72027881, 72027884, 72027886] multipolygon NaN NaN
3687978 POLYGON ((8.69444 47.25146, 8.69413 47.25159, ... customers Männedorf NaN 8708 Bergstrasse NaN parking NaN NaN ... NaN NaN NaN NaN NaN NaN [277518501, 277523899] multipolygon no no
3688008 POLYGON ((8.69676 47.25073, 8.69666 47.25063, ... NaN NaN NaN NaN NaN NaN hospital Südtrakt NaN ... NaN NaN NaN NaN NaN NaN [277523908, 277526894] multipolygon NaN NaN
4458805 MULTIPOLYGON (((8.69064 47.25284, 8.69082 47.2... NaN NaN NaN NaN NaN NaN yes zum Wilden Mann NaN ... NaN NaN NaN NaN NaN NaN [320432370, 320432369] multipolygon NaN NaN
8350122 POLYGON ((8.70188 47.25408, 8.70187 47.25414, ... NaN NaN NaN NaN NaN NaN apartments NaN NaN ... NaN NaN NaN NaN NaN NaN [23794917, 593505479] multipolygon NaN NaN

4211 rows × 140 columns

buildings.geom_type.value_counts()
Polygon         4208
Point              2
MultiPolygon       1
Name: count, dtype: int64
buildings = buildings[buildings.geom_type == "Polygon"].reset_index(drop=True)
buildings = buildings[["geometry"]].to_crs(local_crs)
buildings["uID"] = range(len(buildings))
buildings
geometry uID
0 POLYGON ((2693795.210 1235565.465, 2693791.889... 0
1 POLYGON ((2695894.546 1234627.262, 2695915.207... 1
2 POLYGON ((2695913.787 1234650.423, 2695893.055... 2
3 POLYGON ((2695853.464 1234658.044, 2695874.455... 3
4 POLYGON ((2695859.031 1234690.258, 2695880.444... 4
... ... ...
4203 POLYGON ((2693938.889 1235235.205, 2693936.201... 4203
4204 POLYGON ((2693862.140 1235612.145, 2693856.724... 4204
4205 POLYGON ((2695061.258 1234154.718, 2695037.705... 4205
4206 POLYGON ((2695237.645 1234075.984, 2695230.440... 4206
4207 POLYGON ((2695619.791 1234454.842, 2695618.918... 4207

4208 rows × 2 columns

Streets

In comparison to Martin Fleischmann’s workshop, I here set truncate_by_edge to be True. Depending on the OSM mapping quality of the place at hand, it might also be necessary to change the value of network_type. See geopandas documentation for options.

#osm_graph = osmnx.graph_from_place(place, network_type='drive', truncate_by_edge=True)
#osm_graph = osmnx.graph_from_address(place, network_type='drive', truncate_by_edge=True, dist=dist)
osm_graph = osmnx.graph_from_point(point, network_type='drive', truncate_by_edge=True, dist=dist)
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/osmnx/graph.py:191: FutureWarning: The expected order of coordinates in `bbox` will change in the v2.0.0 release to `(left, bottom, right, top)`.
  G = graph_from_bbox(
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/shapely/predicates.py:798: RuntimeWarning: invalid value encountered in intersects
  return lib.intersects(a, b, **kwargs)
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/shapely/set_operations.py:340: RuntimeWarning: invalid value encountered in union
  return lib.union(a, b, **kwargs)
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/shapely/set_operations.py:340: RuntimeWarning: invalid value encountered in union
  return lib.union(a, b, **kwargs)
osm_graph = osmnx.projection.project_graph(osm_graph, to_crs=local_crs)
streets = osmnx.graph_to_gdfs(
    osm_graph, 
    nodes=False, 
    edges=True,
    node_geometry=False, 
    fill_edge_geometry=True
)
streets.explore()
Make this Notebook Trusted to load map: File -> Trust Notebook
streets
osmid lanes ref name highway maxspeed oneway reversed length geometry bridge junction access
u v key
25428254 25428893 0 37103114 2 7 Weiachstrasse primary 60 False True 283.528 LINESTRING (2692346.132 1263795.144, 2692285.7... NaN NaN NaN
431486791 0 37103140 NaN NaN Allmendstrasse residential NaN False False 86.447 LINESTRING (2692346.132 1263795.144, 2692344.3... NaN NaN NaN
25430556 0 [277655232, 277655235, 277655231] [2, 3] 7 Weiachstrasse primary 60 False False 295.924 LINESTRING (2692346.132 1263795.144, 2692405.8... NaN NaN NaN
25428893 431485940 0 [277655234, 37103108] [1, 2] 7 Weiachstrasse primary 60 True False 174.011 LINESTRING (2692073.821 1263876.753, 2692020.3... NaN NaN NaN
25428254 0 37103114 2 7 Weiachstrasse primary 60 False False 283.528 LINESTRING (2692073.821 1263876.753, 2692162.8... NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
10741066807 303598903 0 1235818305 NaN 7 Weiacherstrasse primary NaN True False 17.393 LINESTRING (2691307.860 1263704.906, 2691311.1... NaN roundabout NaN
2094247362 2094247182 0 199423822 NaN NaN Am Bach residential 30 False True 97.858 LINESTRING (2692523.852 1264985.274, 2692516.2... NaN NaN NaN
2582416633 2582416633 0 252138825 NaN NaN Allmendstrasse residential NaN False False 80.455 LINESTRING (2689973.740 1263824.911, 2689961.8... NaN NaN NaN
1 252138825 NaN NaN Allmendstrasse residential NaN False True 80.455 LINESTRING (2689973.740 1263824.911, 2689953.6... NaN NaN NaN
2582416627 0 252138825 NaN NaN Allmendstrasse residential NaN False True 184.468 LINESTRING (2689973.740 1263824.911, 2690017.3... NaN NaN NaN

674 rows × 13 columns

streets = momepy.remove_false_nodes(streets)
streets = streets[["geometry"]]
streets["nID"] = range(len(streets))
streets
geometry nID
0 LINESTRING (2694366.504 1234661.494, 2694314.6... 0
1 LINESTRING (2694366.504 1234661.494, 2694436.5... 1
2 LINESTRING (2694202.999 1234742.281, 2694190.0... 2
3 LINESTRING (2694202.999 1234742.281, 2694217.3... 3
4 LINESTRING (2694202.999 1234742.281, 2694208.6... 4
... ... ...
973 LINESTRING (2695875.584 1234288.063, 2695878.2... 973
974 LINESTRING (2696072.108 1234083.119, 2696054.3... 974
975 LINESTRING (2695869.423 1234721.748, 2695887.6... 975
976 LINESTRING (2695812.799 1236678.044, 2695817.8... 976
977 LINESTRING (2696403.045 1235648.275, 2696358.4... 977

978 rows × 2 columns

Generated data

Tessellation

We can generate a spatail unit using Voronoi tessellation with given building footprints.

limit = momepy.buffered_limit(buildings, 100)

tessellation = momepy.Tessellation(buildings, "uID", limit, verbose=False, segment=1)
tessellation = tessellation.tessellation
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/shapely/constructive.py:181: RuntimeWarning: invalid value encountered in buffer
  return lib.buffer(
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/shapely/set_operations.py:426: RuntimeWarning: invalid value encountered in unary_union
  return lib.unary_union(collections, **kwargs)
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/1328706492.py:3: FutureWarning: Class based API like `momepy.Tessellation` is deprecated. Replace it with `momepy.morphological_tessellation` or `momepy.enclosed_tessellation` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  tessellation = momepy.Tessellation(buildings, "uID", limit, verbose=False, segment=1)
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/shapely/constructive.py:181: RuntimeWarning: invalid value encountered in buffer
  return lib.buffer(
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/shapely/set_operations.py:133: RuntimeWarning: invalid value encountered in intersection
  return lib.intersection(a, b, **kwargs)
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/1328706492.py:3: UserWarning: Tessellation contains MultiPolygon elements. Initial objects should  be edited. `unique_id` of affected elements: [2347, 340, 2346, 2345, 2471].
  tessellation = momepy.Tessellation(buildings, "uID", limit, verbose=False, segment=1)

Measure

Measure individual morphometric characters.

Dimensions

buildings["area"] = buildings.area
tessellation["area"] = tessellation.area
streets["length"] = streets.length

Shape

buildings['eri'] = momepy.EquivalentRectangularIndex(buildings).series
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/1902756608.py:1: FutureWarning: Class based API like `momepy.EquivalentRectangularIndex` is deprecated. Replace it with `momepy.equivalent_rectangular_index` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  buildings['eri'] = momepy.EquivalentRectangularIndex(buildings).series
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/pandas/core/arraylike.py:492: RuntimeWarning: invalid value encountered in oriented_envelope
  return getattr(ufunc, method)(*new_inputs, **kwargs)
buildings['elongation'] = momepy.Elongation(buildings).series
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/3723954296.py:1: FutureWarning: Class based API like `momepy.Elongation` is deprecated. Replace it with `momepy.elongation` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  buildings['elongation'] = momepy.Elongation(buildings).series
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/pandas/core/arraylike.py:492: RuntimeWarning: invalid value encountered in oriented_envelope
  return getattr(ufunc, method)(*new_inputs, **kwargs)
tessellation['convexity'] = momepy.Convexity(tessellation).series
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/2335586521.py:1: FutureWarning: Class based API like `momepy.Convexity` is deprecated. Replace it with `momepy.convexity` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  tessellation['convexity'] = momepy.Convexity(tessellation).series
streets["linearity"] = momepy.Linearity(streets).series
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/3802614628.py:1: FutureWarning: Class based API like `momepy.Linearity` is deprecated. Replace it with `momepy.linearity` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  streets["linearity"] = momepy.Linearity(streets).series
fig, ax = plt.subplots(1, 2, figsize=(24, 12))

buildings.plot("eri", ax=ax[0], scheme="natural_breaks", legend=True)
buildings.plot("elongation", ax=ax[1], scheme="natural_breaks", legend=True)

ax[0].set_title('Building Equivalent\nRectangular Index', fontsize=20)
ax[1].set_title('Building Elongation', fontsize=20)

ax[0].set_axis_off()
ax[1].set_axis_off()

plt.savefig('../results/Uetikon/eri_and_elongation.svg')  
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/IPython/core/pylabtools.py:77: DeprecationWarning: backend2gui is deprecated since IPython 8.24, backends are managed in matplotlib and can be externally registered.
  warnings.warn(
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/IPython/core/pylabtools.py:77: DeprecationWarning: backend2gui is deprecated since IPython 8.24, backends are managed in matplotlib and can be externally registered.
  warnings.warn(

fig, ax = plt.subplots(1, 2, figsize=(24, 12))

tessellation.plot("convexity", ax=ax[0], scheme="natural_breaks", legend=True)
streets.plot("linearity", ax=ax[1], scheme="natural_breaks", legend=True)

ax[0].set_title('Convexity', fontsize=20)
ax[1].set_title('Linearity', fontsize=20)

ax[0].set_axis_off()
ax[1].set_axis_off()

plt.savefig('../results/Uetikon/convexity_and_linearity.svg')  

Spatial distribution

buildings["shared_walls"] = momepy.SharedWallsRatio(buildings).series
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/momepy/distribution.py:214: FutureWarning: Class based API like `momepy.SharedWalls` or `momepy.SharedWallsRatio` is deprecated. Replace it with `momepy.shared_walls` or explicitly computing `momepy.shared_walls / gdf.length` respectively to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  super().__init__(gdf)
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/shapely/set_operations.py:133: RuntimeWarning: invalid value encountered in intersection
  return lib.intersection(a, b, **kwargs)
buildings.plot("shared_walls", figsize=(12, 12), scheme="natural_breaks", legend=True).set_axis_off()

plt.savefig('../results/Uetikon/sharedwalls.svg')  

Generate spatial weights matrix using libpysal.

queen_1 = libpysal.weights.contiguity.Queen.from_dataframe(tessellation, ids="uID", silence_warnings=True)
tessellation["neighbors"] = momepy.Neighbors(tessellation, queen_1, "uID", weighted=True, verbose=False).series
tessellation["covered_area"] = momepy.CoveredArea(tessellation, queen_1, "uID", verbose=False).series

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    
    buildings["neighbor_distance"] = momepy.NeighborDistance(buildings, queen_1, "uID", verbose=False).series
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/1669107924.py:1: FutureWarning: Class based API like `momepy.Neighbors` is deprecated. Replace it with `momepy.neighbors` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  tessellation["neighbors"] = momepy.Neighbors(tessellation, queen_1, "uID", weighted=True, verbose=False).series
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/1669107924.py:2: FutureWarning: `momepy.CoveredArea` is deprecated. Replace it with `.describe()` method of libpysal.graph.Graph or pin momepy version <1.0. This class will be removed in 1.0. 
  tessellation["covered_area"] = momepy.CoveredArea(tessellation, queen_1, "uID", verbose=False).series
fig, ax = plt.subplots(1, 2, figsize=(24, 12))

buildings.plot("neighbor_distance", ax=ax[0], scheme="natural_breaks", legend=True)
tessellation.plot("covered_area", ax=ax[1], scheme="natural_breaks", legend=True)

ax[0].set_title('Neighbour Distance', fontsize=20)
ax[1].set_title('Covered Area', fontsize=20)


ax[0].set_axis_off()
ax[1].set_axis_off()

plt.savefig('../results/Uetikon/neighbourdist_and_coveredarea.svg')  

queen_3 = momepy.sw_high(k=3, weights=queen_1)
buildings_q1 = libpysal.weights.contiguity.Queen.from_dataframe(buildings, silence_warnings=True)

buildings['interbuilding_distance'] = momepy.MeanInterbuildingDistance(buildings, queen_1, 'uID', queen_3, verbose=False).series
buildings['adjacency'] = momepy.BuildingAdjacency(buildings, queen_3, 'uID', buildings_q1, verbose=False).series
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/3221532125.py:1: FutureWarning: `momepy.sw_high` is deprecated. Replace it with .higher_order() method of libpysal.graph.Graph or pin momepy version <1.0. This class will be removed in 1.0. 
  queen_3 = momepy.sw_high(k=3, weights=queen_1)
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/3221532125.py:2: FutureWarning: `use_index` defaults to False but will default to True in future. Set True/False directly to control this behavior and silence this warning
  buildings_q1 = libpysal.weights.contiguity.Queen.from_dataframe(buildings, silence_warnings=True)
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/3221532125.py:4: FutureWarning: Class based API like `momepy.MeanInterbuildingDistance` is deprecated. Replace it with `momepy.mean_interbuilding_distance` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  buildings['interbuilding_distance'] = momepy.MeanInterbuildingDistance(buildings, queen_1, 'uID', queen_3, verbose=False).series
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/3221532125.py:5: FutureWarning: Class based API like `momepy.BuildingAdjacency` is deprecated. Replace it with `momepy.building_adjacency` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  buildings['adjacency'] = momepy.BuildingAdjacency(buildings, queen_3, 'uID', buildings_q1, verbose=False).series
#fig, ax = plt.subplots(1, 2, figsize=(24, 12))
#
#buildings.plot("interbuilding_distance", ax=ax[0], scheme="natural_breaks", legend=True)
#buildings.plot("adjacency", ax=ax[1], scheme="natural_breaks", legend=True)
#
#ax[0].set_axis_off()
#ax[1].set_axis_off()
profile = momepy.StreetProfile(streets, buildings)
streets["width"] = profile.w
streets["width_deviation"] = profile.wd
streets["openness"] = profile.o
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/2648127835.py:1: FutureWarning: Class based API like `momepy.StreetProfile` is deprecated. Replace it with `momepy.street_profile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  profile = momepy.StreetProfile(streets, buildings)
/Users/mtwente/anaconda3/envs/maxvogt/lib/python3.11/site-packages/pandas/core/arraylike.py:492: RuntimeWarning: invalid value encountered in intersection
  return getattr(ufunc, method)(*new_inputs, **kwargs)
fig, ax = plt.subplots(1, 3, figsize=(24, 12))

streets.plot("width", ax=ax[0], scheme="natural_breaks", legend=True)
streets.plot("width_deviation", ax=ax[1], scheme="natural_breaks", legend=True)
streets.plot("openness", ax=ax[2], scheme="natural_breaks", legend=True)

ax[0].set_title('Width', fontsize=20)
ax[1].set_title('Width Deviation', fontsize=20)
ax[2].set_title('Openness', fontsize=20)

ax[0].set_axis_off()
ax[1].set_axis_off()
ax[2].set_axis_off()

plt.savefig('../results/Uetikon/road_network.svg')  

Intensity

tessellation['car'] = momepy.AreaRatio(tessellation, buildings, 'area', 'area', 'uID').series
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/1819725789.py:1: FutureWarning: `momepy.AreaRatio` is deprecated. Replace it with a direct division of areas or momepy.describe_agg() or pin momepy version <1.0. This class will be removed in 1.0. 
  tessellation['car'] = momepy.AreaRatio(tessellation, buildings, 'area', 'area', 'uID').series
tessellation.plot("car", figsize=(12, 12), vmin=0, vmax=1, legend=True).set_axis_off()
plt.title("Building/Tessellation Area Ratio")

plt.savefig('../results/Uetikon/tessellation_ratio.svg')  

Connectivity

graph = momepy.gdf_to_nx(streets)
graph = momepy.node_degree(graph)
graph = momepy.closeness_centrality(graph, radius=400, distance="mm_len")
graph = momepy.meshedness(graph, radius=400, distance="mm_len")
nodes, streets = momepy.nx_to_gdf(graph)
fig, ax = plt.subplots(1, 3, figsize=(24, 12))

nodes.plot("degree", ax=ax[0], scheme="natural_breaks", legend=True, markersize=1)
nodes.plot("closeness", ax=ax[1], scheme="natural_breaks", legend=True, markersize=1, legend_kwds={"fmt": "{:.6f}"})
nodes.plot("meshedness", ax=ax[2], scheme="natural_breaks", legend=True, markersize=1)

ax[0].set_axis_off()
ax[1].set_axis_off()
ax[2].set_axis_off()

plt.savefig('../results/Uetikon/connectivity.svg')  

buildings["nodeID"] = momepy.get_node_id(buildings, nodes, streets, "nodeID", "nID")
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/403706460.py:1: FutureWarning: Class based API like `momepy.get_node_id` is deprecated. Replace it with `momepy.get_nearest_node` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  buildings["nodeID"] = momepy.get_node_id(buildings, nodes, streets, "nodeID", "nID")

Link all data together (to tessellation cells or buildings).

tessellation
uID geometry nID area convexity neighbors covered_area car
0 114 POLYGON ((2695834.746 1233625.421, 2695834.355... 290.0 14529.059499 0.960915 0.012374 49413.369297 0.061070
1 3871 POLYGON ((2695912.926 1233680.031, 2695912.103... 290.0 16154.907417 0.991230 0.008214 44341.801643 0.030395
2 113 POLYGON ((2695716.487 1233622.470, 2695714.872... 290.0 6049.056428 0.938012 0.013623 31319.031254 0.083529
3 85 POLYGON ((2695885.722 1233697.108, 2695884.820... 290.0 1151.457783 0.959460 0.028674 36242.691622 0.235791
4 86 POLYGON ((2695859.675 1233704.471, 2695859.293... 290.0 1210.009533 0.929897 0.034027 23280.238790 0.222167
... ... ... ... ... ... ... ... ...
4203 1730 POLYGON ((2693502.193 1236665.263, 2693501.147... 882.0 30279.560171 0.999617 0.006327 79420.950415 0.002960
4204 1737 POLYGON ((2693217.394 1236885.063, 2693214.830... 887.0 10909.899358 0.875260 0.006216 77137.743463 0.016618
4205 2834 POLYGON ((2693015.371 1236882.403, 2693020.965... 887.0 31272.406032 0.998710 0.006240 95116.350768 0.003120
4206 1736 POLYGON ((2693305.911 1236902.127, 2693304.809... 887.0 17301.667012 0.995261 0.003879 45865.337431 0.004820
4207 3040 POLYGON ((2692964.604 1236996.397, 2692962.494... 872.0 27157.256090 0.925868 0.001520 44505.341063 0.019216

4208 rows × 8 columns

merged = tessellation.merge(buildings.drop(columns=['nID', 'geometry']), on='uID')
merged = merged.merge(streets.drop(columns='geometry'), on='nID', how='left')
merged = merged.merge(nodes.drop(columns='geometry'), on='nodeID', how='left')
merged.columns
Index(['uID', 'geometry', 'nID', 'area_x', 'convexity', 'neighbors',
       'covered_area', 'car', 'area_y', 'eri', 'elongation', 'shared_walls',
       'neighbor_distance', 'interbuilding_distance', 'adjacency', 'nodeID',
       'length', 'linearity', 'width', 'width_deviation', 'openness', 'mm_len',
       'node_start', 'node_end', 'x', 'y', 'degree', 'closeness',
       'meshedness'],
      dtype='object')

Understanding the context

Measure first, second and third quartile of distribution of values within an area around each building.

percentiles = []
for column in merged.columns.drop(["uID", "nodeID", "nID", 'mm_len', 'node_start', 'node_end', "geometry"]):
    perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
    perc.columns = [f"{column}_" + str(x) for x in perc.columns]
    percentiles.append(perc)
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
/var/folders/vs/1r1r5k5x12v5dj0w9z79tj3c0000gn/T/ipykernel_47370/962013058.py:3: FutureWarning: Class based API like `momepy.Percentiles` is deprecated. Replace it with `momepy.percentile` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0. 
  perc = momepy.Percentiles(merged, column, queen_3, "uID", verbose=False).frame
percentiles_joined = pandas.concat(percentiles, axis=1)
percentiles_joined
area_x_25 area_x_50 area_x_75 convexity_25 convexity_50 convexity_75 neighbors_25 neighbors_50 neighbors_75 covered_area_25 ... y_75 degree_25 degree_50 degree_75 closeness_25 closeness_50 closeness_75 meshedness_25 meshedness_50 meshedness_75
0 1234.195711 1697.119772 6572.921216 0.927708 0.948499 0.963880 0.015597 0.033700 0.041871 13172.287270 ... 1.233838e+06 4.5 6.0 6.0 0.000075 0.000107 0.000115 0.666667 0.736842 0.736842
1 1180.733658 1596.227293 5129.051764 0.931741 0.952624 0.968881 0.018242 0.033700 0.040639 13172.287270 ... 1.233838e+06 4.0 5.0 6.0 0.000075 0.000109 0.000115 0.666667 0.736842 0.736842
2 1377.199665 1596.548889 2412.642588 0.933585 0.951027 0.963410 0.024026 0.034614 0.042003 12888.927072 ... 1.233796e+06 6.0 6.0 6.0 0.000075 0.000075 0.000115 0.666667 0.666667 0.736842
3 1180.733658 1697.119772 7628.306210 0.935449 0.952624 0.963880 0.015597 0.031154 0.040639 13588.515771 ... 1.233817e+06 4.0 6.0 6.0 0.000062 0.000107 0.000115 0.666667 0.736842 0.757310
4 1180.733658 1596.227293 4623.156909 0.931741 0.948499 0.960188 0.018242 0.033700 0.042307 13172.287270 ... 1.233838e+06 4.5 6.0 6.0 0.000075 0.000109 0.000115 0.666667 0.736842 0.736842
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
4203 3552.112057 6538.909867 12081.342732 0.924914 0.979721 0.992960 0.010532 0.016468 0.020503 32398.112727 ... 1.236643e+06 1.0 5.0 5.0 0.000036 0.000072 0.000137 0.400000 0.428571 0.448276
4204 13138.089645 17500.928017 23062.392331 0.969921 0.994111 0.997345 0.005048 0.007052 0.010812 74864.937485 ... 1.236695e+06 3.0 5.0 5.0 0.000030 0.000137 0.000137 0.428571 0.448276 0.448276
4205 10658.242967 17301.667012 18449.858958 0.956905 0.992204 0.996751 0.006228 0.009274 0.012447 45374.852644 ... 1.236643e+06 5.0 5.0 5.0 0.000041 0.000137 0.000137 0.438424 0.448276 0.448276
4206 13138.089645 17348.084973 18449.858958 0.989153 0.995261 0.997345 0.006228 0.007776 0.010812 76457.745279 ... 1.236643e+06 5.0 5.0 5.0 0.000041 0.000137 0.000137 0.438424 0.448276 0.448276
4207 17500.928017 17792.980202 22544.722717 0.957433 0.990979 0.994926 0.004983 0.006228 0.007008 76457.745279 ... 1.236720e+06 3.0 5.0 5.0 0.000068 0.000137 0.000137 0.224138 0.448276 0.448276

4208 rows × 66 columns

fig, ax = plt.subplots(1, 2, figsize=(24, 12))

tessellation.plot("convexity", ax=ax[0], scheme="natural_breaks", legend=True)
merged.plot(percentiles_joined['convexity_50'].values, ax=ax[1], scheme="natural_breaks", legend=True)

ax[0].set_title('Convexity', fontsize=20)
ax[1].set_title('Convexity_50', fontsize=20)

ax[0].set_axis_off()
ax[1].set_axis_off()

plt.savefig('../results/Uetikon/convexity.svg')  

Clustering

Standardize values before clustering.

standardized = (percentiles_joined - percentiles_joined.mean()) / percentiles_joined.std()
standardized
area_x_25 area_x_50 area_x_75 convexity_25 convexity_50 convexity_75 neighbors_25 neighbors_50 neighbors_75 covered_area_25 ... y_75 degree_25 degree_50 degree_75 closeness_25 closeness_50 closeness_75 meshedness_25 meshedness_50 meshedness_75
0 0.362944 0.089853 1.423248 0.164120 -0.627222 -1.391476 -1.721919 -0.668029 -0.724829 0.637868 ... -2.239376 0.207254 0.565778 -0.028334 -0.404747 -0.254115 -0.432977 0.830266 0.704569 0.287146
1 0.302180 0.015336 0.862344 0.450249 -0.207035 -0.699938 -1.435731 -0.668029 -0.844019 0.637868 ... -2.239376 -0.062567 -0.304326 -0.028334 -0.404747 -0.219437 -0.432977 0.830266 0.704569 0.287146
2 0.525481 0.015573 -0.192907 0.581107 -0.369697 -1.456348 -0.809867 -0.572934 -0.712131 0.591785 ... -2.298533 1.016720 0.565778 -0.028334 -0.404747 -0.761446 -0.432977 0.830266 0.349721 0.287146
3 0.302180 0.089853 1.833236 0.713364 -0.207035 -1.391476 -1.721919 -0.932563 -0.844019 0.705560 ... -2.268955 -0.062567 0.565778 -0.028334 -0.633441 -0.254115 -0.432977 0.830266 0.704569 0.381046
4 0.302180 0.015336 0.665817 0.450249 -0.627222 -1.901949 -1.435731 -0.668029 -0.682725 0.637868 ... -2.239376 0.207254 0.565778 -0.028334 -0.404747 -0.219437 -0.432977 0.830266 0.704569 0.287146
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
4203 2.997463 3.665896 3.563120 -0.034126 2.553274 2.629306 -2.269881 -2.459062 -2.791308 3.764595 ... 1.643464 -1.681499 -0.304326 -1.172765 -1.092845 -0.810939 -0.094167 -0.703399 -0.854227 -1.036712
4204 13.892782 11.762210 7.828958 3.159418 4.019154 3.235623 -2.863324 -3.437832 -3.728438 10.671043 ... 1.715927 -0.602211 -0.304326 -1.172765 -1.196603 0.231723 -0.094167 -0.539077 -0.754590 -1.036712
4205 11.074215 11.615040 6.037115 2.235847 3.824983 3.153393 -2.735624 -3.206781 -3.570320 5.875023 ... 1.643464 0.477076 -0.304326 -1.172765 -1.010950 0.231723 -0.094167 -0.482415 -0.754590 -1.036712
4206 13.892782 11.649323 6.037115 4.524077 4.136345 3.235623 -2.735624 -3.362513 -3.728438 10.930084 ... 1.643464 0.477076 -0.304326 -1.172765 -1.010950 0.231723 -0.094167 -0.482415 -0.754590 -1.036712
4207 18.851538 11.977913 7.627858 2.273285 3.700134 2.901182 -2.870351 -3.523436 -4.096357 10.930084 ... 1.749679 -0.602211 -0.304326 -1.172765 -0.527176 0.231723 -0.094167 -1.714824 -0.754590 -1.036712

4208 rows × 66 columns

How many clusters?

cgram = Clustergram(range(1, 12), n_init=10, random_state=0)
cgram.fit(standardized.fillna(0))
K=1 skipped. Mean computed from data directly.
K=2 fitted in 0.049 seconds.
K=3 fitted in 0.047 seconds.
K=4 fitted in 0.049 seconds.
K=5 fitted in 0.071 seconds.
K=6 fitted in 0.125 seconds.
K=7 fitted in 0.090 seconds.
K=8 fitted in 0.118 seconds.
K=9 fitted in 0.088 seconds.
K=10 fitted in 0.102 seconds.
K=11 fitted in 0.104 seconds.
Clustergram(k_range=range(1, 12), backend='sklearn', method='kmeans', kwargs={'n_init': 10, 'random_state': 0})
show(cgram.bokeh())
cgram.labels.head()
1 2 3 4 5 6 7 8 9 10 11
0 0 0 0 0 1 5 2 7 8 5 3
1 0 0 0 0 1 5 2 7 8 5 3
2 0 0 0 0 0 5 2 7 8 5 3
3 0 0 0 0 1 5 2 7 8 5 3
4 0 0 0 0 0 5 2 7 8 5 3
merged["cluster"] = cgram.labels[8].values
urban_types = buildings[["geometry", "uID"]].merge(merged[["uID", "cluster"]], on="uID")
urban_types.explore("cluster", categorical=True, prefer_canvas=True, tiles="CartoDB Positron", tooltip=False)
Make this Notebook Trusted to load map: File -> Trust Notebook

Export

Save cluster output geodata to geojson file:

urban_types.to_file('../data/raw/Uetikon.geojson', driver='GeoJSON')

Utilities

Once you run the cell below, it hides all cells below the currently active one (aka presentation mode).

%%html
<style>
.jp-Cell.jp-mod-selected ~ .jp-Cell {
    display: none;
}
</style>

Run this cell to turn the presentation mode off.

%%html
<style>
.jp-Cell.jp-mod-selected ~ .jp-Cell {
    display: block;
}
</style>
Back to top